查看原文
其他

整天为所欲为的AccessibilityService 你了解原理吗?

张朝旭 鸿洋 2019-04-05

本文作者


作者:张朝旭

链接:

https://juejin.im/post/5b27bfc56fb9a00e373bd232

本文由作者授权发布。


1本文需要解决的问题


之前本人做了一个项目,需要用到AccessibilityService这个系统提供的拓展服务。这个服务本意是作为Android系统的一个辅助功能,去帮助残疾人更好地使用手机。但是由于它的一些特性,给很多项目的实现提供了一个新的思路,例如之前大名鼎鼎的微信抢红包插件,本质上就是使用了这个服务。我研究AccessibilityService的目的是解决以下几个我在使用过程中所思考的问题:


  1. AccessibilityService这个Service跟一般的Service有什么区别?

  2. AccessibilityService是如何做到监控并捕捉用户行为的?

  3. AccessibilityService是如何做到查找控件,执行点击等操作的?


2初步分析


本文基于Android 7.1的源码对AccessibilityService进行分析。 为了更好地理解和分析代码,我写了一个demo,如果想学习具体的使用方法,可以参考Google官方文档AccessibilityService。本文不做AccessibilityService的具体使用教程。


创建AccessibilityService

public class MyAccessibilityService extends AccessibilityService {

    private static final String TAG = "MyAccessibilityService";

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, "onCreate");
    }

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        int eventType = event.getEventType();
        switch (eventType) {
            case AccessibilityEvent.TYPE_VIEW_CLICKED:
                // 捕获到点击事件
                Log.i(TAG, "capture click event!");
                AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
                if (nodeInfo != null) {
                    // 查找text为Test!的控件
                    List<AccessibilityNodeInfo> button = nodeInfo.findAccessibilityNodeInfosByText("Test!");
                    nodeInfo.recycle();
                    for (AccessibilityNodeInfo item : button) {
                        Log.i(TAG, "long-click button!");
                        // 执行长按操作
                        item.performAction(AccessibilityNodeInfo.ACTION_LONG_CLICK);
                    }
                }
                break;
            default:
                break;
        }
    }

    @Override
    public void onInterrupt() {
        Log.i(TAG, "onInterrupt");
    }
}


可以看到,当我们捕获到click 事件的时候,会将其换成longclick 事件。


AccessibilityService配置


res/xml/accessibility_service_config.xml

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeAllMask"
    android:accessibilityFeedbackType="feedbackSpoken"
    android:accessibilityFlags="flagRetrieveInteractiveWindows|flagRequestFilterKeyEvents"
    android:canRequestFilterKeyEvents="true"
    android:canRetrieveWindowContent="true"
    android:description="@string/app_name"
    android:notificationTimeout="100"
    android:packageNames="com.xu.accessibilitydemo" />


在manifest中进行注册


<service
    android:name=".MyAccessibilityService"
    android:enabled="true"
    android:exported="true"
    android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">

    <intent-filter>
        <action android:name="android.accessibilityservice.AccessibilityService"/>
    </intent-filter>

     <meta-data
         android:name="android.accessibilityservice"
         android:resource="@xml/accessibility_service_config"/>

</service>


创建一个text为Test!的button控件,设置监听方法:

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    private Button button;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        button = findViewById(R.id.button);

        button.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                Log.i(TAG, "onLongClick");
                return false;
            }
        });

    }
}


开启AccessibilityService


开启AccessibilityService有两种方法,一种是在代码中开启,另一种是手动开启,具体开启位置为设置--无障碍中开启。


运行应用,点击text为Test!的按钮


会出现以下的日志:



具体解释:点击按钮即产生TYPE_VIEW_CLICKED事件 --> 被AcceesibilityService捕获 --> 捕获后执行长按按钮操作 --> 执行长按回调方法。


为什么AcceesibilityService能捕获并执行其他操作呢,接下来我将对源码进行解析~


3源码解析


3.1 AccessibilityService内部逻辑

AccessibilityService.java

public abstract class AccessibilityService extends Service {
      // 省略代码
      public abstract void onAccessibilityEvent(AccessibilityEvent event);

      public abstract void onInterrupt();

      @Override
      public final IBinder onBind(Intent intent) {
          return new IAccessibilityServiceClientWrapper(this, getMainLooper(), new Callbacks() {
              @Override
              public void onServiceConnected() {
                  AccessibilityService.this.dispatchServiceConnected();
              }

              @Override
              public void onInterrupt() {
                  AccessibilityService.this.onInterrupt();
              }

              @Override
              public void onAccessibilityEvent(AccessibilityEvent event) {
                  AccessibilityService.this.onAccessibilityEvent(event);
              }

              @Override
              public void init(int connectionId, IBinder windowToken) {
                  mConnectionId = connectionId;
                  mWindowToken = windowToken;

                  // The client may have already obtained the window manager, so
                  // update the default token on whatever manager we gave them.
                  final WindowManagerImpl wm = (WindowManagerImpl) getSystemService(WINDOW_SERVICE);
                  wm.setDefaultToken(windowToken);
              }

              @Override
              public boolean onGesture(int gestureId) {
                  return AccessibilityService.this.onGesture(gestureId);
              }

              @Override
              public boolean onKeyEvent(KeyEvent event) {
                  return AccessibilityService.this.onKeyEvent(event);
              }

              @Override
              public void onMagnificationChanged(@NonNull Region region,
                      float scale, float centerX, float centerY) 
{
                  AccessibilityService.this.onMagnificationChanged(region, scale, centerX, centerY);
              }

              @Override
              public void onSoftKeyboardShowModeChanged(int showMode) {
                  AccessibilityService.this.onSoftKeyboardShowModeChanged(showMode);
              }

              @Override
              public void onPerformGestureResult(int sequence, boolean completedSuccessfully) {
                  AccessibilityService.this.onPerformGestureResult(sequence, completedSuccessfully);
              }
          });
      }
}

分析:

  1. AccessibilityService是一个抽象类,继承于Service,提供两个抽象方法 onAccessibilityEvent() 和  onInterrupt();

  2. 虽然是抽象类,但是实现了最重要的 onBind() 方法,在其中创建了一个IAccessibilityServiceClientWrapper对象,实现Callbacks接口中的抽象方法。


IAccessibilityServiceClientWrapper

// 以分析onAccessibilityEvent为例,省略部分代码
public static class IAccessibilityServiceClientWrapper extends IAccessibilityServiceClient.Stub
            implements HandlerCaller.Callback 
{
    private final HandlerCaller mCaller;
    private final Callbacks mCallback;
    private int mConnectionId;

    public IAccessibilityServiceClientWrapper(Context context, Looper looper,
                Callbacks callback) 
{
        mCallback = callback;
        mCaller = new HandlerCaller(context, looper, thistrue /*asyncHandler*/);
    }

    public void init(IAccessibilityServiceConnection connection, int connectionId,
                IBinder windowToken) 
{
        Message message = mCaller.obtainMessageIOO(DO_INIT, connectionId,
                    connection, windowToken);
        mCaller.sendMessage(message);
    }

    // 省略部分代码 

    public void onAccessibilityEvent(AccessibilityEvent event) {
        Message message = mCaller.obtainMessageO(DO_ON_ACCESSIBILITY_EVENT, event);
        mCaller.sendMessage(message);
    }

    @Override
    public void executeMessage(Message message) {
        switch (message.what) {
            case DO_ON_ACCESSIBILITY_EVENT: {
                AccessibilityEvent event = (AccessibilityEvent) message.obj;
                if (event != null) {
                    AccessibilityInteractionClient.getInstance().onAccessibilityEvent(event);
                    mCallback.onAccessibilityEvent(event);
                    // Make sure the event is recycled.
                    try {
                        event.recycle();
                    } catch (IllegalStateException ise) {
                        /* ignore - best effort */
                    }
                }
            } return;
            // ...         
        }
     }
}

分析

1. IAccessibilityServiceClientWrapper继承于IAccessibilityServiceClient类,它是一个aidl接口,同时注意到它是继承于IAccessibilityServiceClient.Stub类可以大概猜测到,AccessibilityService为一个远程Service,使用到跨进程通信技术,后面我还会继续分析这个;


2. IAccessibilityServiceClientWrapper的类构造方法中,有两个比较重要的参数,一个是looper,另一个是Callbacks callback。Looper不用说,而Callbacks接口定义了很多方法,代码如下:


public interface Callbacks {
    public void onAccessibilityEvent(AccessibilityEvent event);
    public void onInterrupt();
    public void onServiceConnected();
    public void init(int connectionId, IBinder windowToken);
    public boolean onGesture(int gestureId);
    public boolean onKeyEvent(KeyEvent event);
    public void onMagnificationChanged(@NonNull Region region,
                float scale, float centerX, float centerY)
;
    public void onSoftKeyboardShowModeChanged(int showMode);
    public void onPerformGestureResult(int sequence, boolean completedSuccessfully);
}


3. IAccessibilityServiceClientWrapper同时也实现了HandlerCaller.Callback接口,HandlerCaller类通过命名也可以知道,它内部含有一个Handler实例,所以可以把它当做一个Handler,而处理信息的方法就是HandlerCaller.Callback#executeMessage(msg)方法


4. 代码有点绕,故简单总结一下流程:


AccessibilityEvent产生 
-> Binder驱动  
->IAccessibilityServiceClientWrapper#onAccessibilityEvent(AccessibilityEvent) 
-> HandlerCaller#sendMessage(message); // message中包括AccessibilityEvent 
-> IAccessibilityServiceClientWrapper#executeMessage();       
-> Callbacks#onAccessibilityEvent(event);        
-> AccessibilityService.this.onAccessibilityEvent(event);


到这里解决了我们的第一个问题:AccessibilityService同样继承于Service类,它属于远程服务类,是Android系统提供的一种服务,可以绑定此服务,用于捕捉界面的一些特定事件。


3.2 AccessibilityService外部逻辑


前面分析了接收到AccessibilityEvent之后的代码逻辑,那么,这些AccessibilityEvent是怎样产生的呢,而且,在回调执行之后是怎么做到点击等操作的(如demo所示)?我们接下来继续分析相关的源码~


我们从demo作为例子开始入手,首先我们知道,一个点击事件的产生,实际代码逻辑是在View#onTouchEvent() -> View#performClick()中:

public boolean performClick() {
    final boolean result;
    final ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnClickListener != null) {
        playSoundEffect(SoundEffectConstants.CLICK);
        li.mOnClickListener.onClick(this);
        result = true;
    } else {
        result = false;
    }
    // !!!
    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
    return result;
}


这里找到一个重点方法sendAccessibilityEvent(),继续跟进去,最后走到View#sendAccessibilityEventUncheckedInternal()方法:

public void sendAccessibilityEventUncheckedInternal(AccessibilityEvent event) {
    // 省略一堆代码
    // In the beginning we called #isShown(), so we know that getParent() is not null.
    getParent().requestSendAccessibilityEvent(this, event);
}


这里的getParent()会返回一个实现ViewParent接口的对象。 我们可以简单理解为,它会让View的父类执行requestSendAccessibilityEvent()方法,而View的父类一般为ViewGroup,我们查看ViewGroup#requestSendAccessibilityEvent()方法:

@Override
public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) {
    ViewParent parent = mParent;
    
// 省略一堆代码
    
return parent.requestSendAccessibilityEvent(this, event);
}


这里涉及到一个变量mParent,我们要找到这个mParent变量是在哪里被赋值的。 首先我们在View类中找到一个相关的方法View#assignParent():


void assignParent(ViewParent parent) {
    if (mParent == null) {
        mParent = parent;
    } else if (parent == null) {
        mParent = null;
    } else {
        throw new RuntimeException("view " + this + " being added, but" + " it already has a parent");
    }
}


但是View类中并没有调用此方法,猜测是View的父类进行调用。 通过对源码进行搜索,发现最后是在ViewRootImpl#setView()中进行调用,赋值的是this即ViewRootImpl本身。 直接跳到ViewRootImpl#requestSendAccessibilityEvent()方法:

@Override
public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) {
    // ... 省略一堆堆代码
    // !!!
    mAccessibilityManager.sendAccessibilityEvent(event);
    return true;
}


重点:

AccessibilityManager#sendAccessibilityEvent(event)

public void sendAccessibilityEvent(AccessibilityEvent event) {
    final IAccessibilityManager service;
    final int userId;
    synchronized(mLock) {
        service = getServiceLocked();
        userId = mUserId;
    }
    boolean doRecycle = false;
    try {
        event.setEventTime(SystemClock.uptimeMillis());

        // !!!
        doRecycle = service.sendAccessibilityEvent(event, userId);
        Binder.restoreCallingIdentity(identityToken);
     
    } catch (RemoteException re) {
        Log.e(LOG_TAG, "Error during sending " + event + " ", re);
    } 
}

private IAccessibilityManager getServiceLocked() {
    if (mService == null) {
        tryConnectToServiceLocked(null);
    }
    return mService;
}

private void tryConnectToServiceLocked(IAccessibilityManager service) {
    if (service == null) {
        IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
        if (iBinder == null) {
            return;
        }
        service = IAccessibilityManager.Stub.asInterface(iBinder);
    }
    try {
        final int stateFlags = service.addClient(mClient, mUserId);
        setStateLocked(stateFlags);
        mService = service;
    } catch (RemoteException re) {
        Log.e(LOG_TAG, "AccessibilityManagerService is dead", re);
    }
}


这里有使用到Android Binder机制,看到 Stub.asInterface就了解到这是 Binder 的客户端了,重点为IAccessibilityManager#sendAccessibilityEvent()方法,这里调用的是代理方法,实际代码逻辑在AccessibilityManagerService#sendAccessibilityEvent():

@Override
public boolean sendAccessibilityEvent(AccessibilityEvent event, int userId) {
    synchronized(mLock) {
      
        if (mSecurityPolicy.canDispatchAccessibilityEventLocked(event)) {
            mSecurityPolicy.updateActiveAndAccessibilityFocusedWindowLocked(event.getWindowId(), event.getSourceNodeId(), event.getEventType(), event.getAction());
            mSecurityPolicy.updateEventSourceLocked(event);
            // !!!
            notifyAccessibilityServicesDelayedLocked(event, false);
            notifyAccessibilityServicesDelayedLocked(event, true);
        }
        event.recycle();
    }
    return (OWN_PROCESS_ID != Binder.getCallingPid());
}

private void notifyAccessibilityServicesDelayedLocked(AccessibilityEvent event, boolean isDefault) {
    try {
        UserState state = getCurrentUserStateLocked();
        for (int i = 0, count = state.mBoundServices.size(); i < count; i++) {
            Service service = state.mBoundServices.get(i);
            if (service.mIsDefault == isDefault) {
                if (canDispatchEventToServiceLocked(service, event)) {
                    service.notifyAccessibilityEvent(event);
                }
            }
        }
    } catch (IndexOutOfBoundsException oobe) {
        // An out of bounds exception can happen if services are going away
        // as the for loop is running. If that happens, just bail because
        // there are no more services to notify.
    }
}


1.在方法中,最后会调用notifyAccessibilityServicesDelayedLocked()方法,然后将event进行回收;


2. 在notifyAccessibilityServicesDelayedLocked()方法中,会获得所有Bound即绑定的Service,执行notifyAccessibilityEvent()方法,通过跟踪代码逻辑,最后会调用绑定Service的onAccessibilityEvent()方法。绑定的Service是指我们自己实现的继承于AccessibilityService的Service类,当你在设置-无障碍中开启服务之后即将服务绑定到AccessibilityManagerService中。


这样我们解决了第二个问题:AccessibilityService是如何做到监控捕捉用户行为的:(以点击事件为例)


AccessibilityEvent产生:

View#performClick()   
-> View#sendAccessibilityEventUncheckedInternal()    
-> ViewGroup#requestSendAccessibilityEvent()     
-> ViewRootImpl#requestSendAccessibilityEvent()      
-> AccessibilityManager#sendAccessibilityEvent(event)       
-> AccessibilityManagerService#sendAccessibilityEvent()        
-> AccessibilityManagerService#notifyAccessibilityServicesDelayedLocked()         
-> Service#notifyAccessibilityEvent(event)


AccessibilityEvent处理:

AccessibilityEvent   
-> Binder驱动    
-> IAccessibilityServiceClientWrapper#onAccessibilityEvent(AccessibilityEvent)     
-> HandlerCaller#sendMessage(message); // message中包括AccessibilityEvent      
-> IAccessibilityServiceClientWrapper#executeMessage();       
-> Callbacks#onAccessibilityEvent(event);        
-> AccessibilityService.this.onAccessibilityEvent(event);


作者原文还有分析如何查找控件以及一些有用代码记录,不过由于文章篇幅限制,可以通过阅读原文查看。


文章代码有点多,以下由鸿洋我来给大家总结下:


当有人问你 AccessibilityService 的原理时?


其实也就是一个 Service 的子类,就像我们平时 bindService 一样,其核心代码就是复写 onBind方法,返回一个Binder对象,其实和我们平时写aidl 很类似,返回的是 IAccessibilityServiceClient.Stub 对象


记住我们启动了一个远程 Service,等着客户端 bindService。


而当点击我们的 View 的时候,拿到 Event,辗转通过 ServiceManager 拿到AccessibilityManagerService 的代理对象与AccessibilityManagerService交互,而其内部维护的 UserState.mBoundServices为内部类 Service继承自ServiceConnection,内部包含 bindService(accessibilityService)代码,并在 onServiceConnected中通过IAccessibilityServiceClient.Stub.asInterface,这样就看到客户端binder 对象了,这样就可以和我们的服务端binder 交互了。


简言之,你可以认为本质上就是 bindService通信!


核心代码都在AccessibilityManagerService中。


推荐阅读

推荐几个好用的 Studio 插件

是时候来学习 Kotlin 了

性能优化技巧知识梳理


扫一扫 关注我的公众号

如果你想要跟大家分享你的文章,欢迎投稿~


┏(^0^)┛明天见!


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存